package auth

import (
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"gitee.com/zfd81/dbrs/engine"

	"gopkg.in/src-d/go-errors.v1"
	"vitess.io/vitess/go/mysql"
)

var (
	regNative = regexp.MustCompile(`^\*[0-9A-F]{40}$`)

	// ErrParseUserFile is given when user file is malformed.
	ErrParseUserFile = errors.NewKind("error parsing user file")
	// ErrUnknownPermission happens when a user permission is not defined.
	ErrUnknownPermission = errors.NewKind("unknown permission, %s")
	// ErrDuplicateUser happens when a user appears more than once.
	ErrDuplicateUser = errors.NewKind("duplicate user, %s")
)

// nativeUser holds information about credentials and permissions for a user.
type nativeUser struct {
	Name            string
	Password        string
	JSONPermissions []string `json:"Permissions"`
	Permissions     Permission
}

// Allowed checks if the user has certain permission.
func (u nativeUser) Allowed(p Permission) error {
	if u.Permissions&p == p {
		return nil
	}

	// permissions needed but not granted to the user
	p2 := (^u.Permissions) & p

	return ErrNotAuthorized.Wrap(ErrNoPermission.New(p2))
}

// NativePassword generates a mysql_native_password string.
func NativePassword(password string) string {
	if len(password) == 0 {
		return ""
	}

	// native = sha1(sha1(password))

	hash := sha1.New()
	hash.Write([]byte(password))
	s1 := hash.Sum(nil)

	hash.Reset()
	hash.Write(s1)
	s2 := hash.Sum(nil)

	s := strings.ToUpper(hex.EncodeToString(s2))

	return fmt.Sprintf("*%s", s)
}

// Native holds mysql_native_password users.
type Native struct {
	users map[string]nativeUser
}

// NewNativeSingle creates a NativeAuth with a single user with given
// permissions.
func NewNativeSingle() *Native {
	return &Native{make(map[string]nativeUser)}
}

func (s *Native) AddUser(name, password string, perm Permission) {
	s.users[name] = nativeUser{
		Name:        name,
		Password:    NativePassword(password),
		Permissions: perm,
	}
}

// Mysql implements Auth interface.
func (s *Native) Mysql() mysql.AuthServer {
	entries := make(map[string][]*mysql.AuthServerStaticEntry)
	for k, v := range s.users {
		entries[k] = []*mysql.AuthServerStaticEntry{
			{
				MysqlNativePassword: v.Password,
				Password:            v.Password,
			},
		}
	}
	bytes, _ := json.Marshal(entries)
	return NewAuthServerStatic("", string(bytes), 0)
}

// Allowed implements Auth interface.
func (s *Native) Allowed(ctx *engine.Context, permission Permission) error {
	name := ctx.Client().User
	u, ok := s.users[name]
	if !ok {
		return ErrNotAuthorized.Wrap(ErrNoPermission.New(permission))
	}

	return u.Allowed(permission)
}
